package com.firefly.core.support.xml.parse;

import static com.firefly.core.support.xml.parse.XmlNodeConstants.ARGUMENT_ELEMENT;
import static com.firefly.core.support.xml.parse.XmlNodeConstants.CLASS_ATTRIBUTE;
import static com.firefly.core.support.xml.parse.XmlNodeConstants.CONTRUCTOR_ELEMENT;
import static com.firefly.core.support.xml.parse.XmlNodeConstants.ID_ATTRIBUTE;
import static com.firefly.core.support.xml.parse.XmlNodeConstants.NAME_ATTRIBUTE;
import static com.firefly.core.support.xml.parse.XmlNodeConstants.PROPERTY_ELEMENT;
import static com.firefly.core.support.xml.parse.XmlNodeConstants.REF_ATTRIBUTE;
import static com.firefly.core.support.xml.parse.XmlNodeConstants.TYPE_ATTRIBUTE;
import static com.firefly.core.support.xml.parse.XmlNodeConstants.VALUE_ATTRIBUTE;

import java.util.ArrayList;
import java.util.List;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.firefly.core.support.xml.ManagedRef;
import com.firefly.core.support.xml.ManagedValue;
import com.firefly.core.support.xml.XmlBeanDefinition;
import com.firefly.core.support.xml.XmlBeanReader;
import com.firefly.core.support.xml.XmlGenericBeanDefinition;
import com.firefly.core.support.xml.XmlManagedNode;
import com.firefly.utils.ReflectUtils;
import com.firefly.utils.StringUtils;
import com.firefly.utils.VerifyUtils;
import com.firefly.utils.dom.Dom;

public class BeanNodeParser extends AbstractXmlNodeParser implements XmlNodeParser {

	@Override
	public Object parse(Element ele, Dom dom) {
		// gets basic attribute
		String id = ele.getAttribute(ID_ATTRIBUTE);
		String className = ele.getAttribute(CLASS_ATTRIBUTE);
		XmlBeanDefinition xmlBeanDefinition = new XmlGenericBeanDefinition();
		xmlBeanDefinition.setId(id);
		xmlBeanDefinition.setClassName(className);

		
		Class<?> clazz = null;
		log.info("classes [{}]", className);
		try {
			clazz = XmlBeanReader.class.getClassLoader().loadClass(className);
		} catch (Throwable e) {
			error("loads class \"" + className + "\" error");
		}
		
		// gets bean's constructor
		List<Element> constructors = dom.elements(ele, CONTRUCTOR_ELEMENT);
		if(constructors != null && constructors.size() > 0) {
			Element constructorElement = constructors.get(0);
			List<Element> arguments = dom.elements(constructorElement, ARGUMENT_ELEMENT);
			if(arguments != null && arguments.size() >= 1) {
				List<Class<?>> argsClass = new ArrayList<Class<?>>();
				for(Element argument : arguments) {
					XmlManagedNode xmlManagedNode = parseXmlManagedNode(argument, dom);
					if(xmlManagedNode != null) {
						xmlBeanDefinition.getContructorParameters().add(xmlManagedNode);
					}
					String initArgsType = argument.getAttribute(TYPE_ATTRIBUTE);
					if(VerifyUtils.isNotEmpty(initArgsType)) {
						try {
							argsClass.add(XmlBeanReader.class.getClassLoader().loadClass(initArgsType));
						} catch (ClassNotFoundException e) {
							error("The '" + initArgsType + "' not found");
						}
					} else {
						error("The '" + className + "' constructor argument node MUST has type attribute");
					}
				}
				try {
					xmlBeanDefinition.setConstructor(clazz.getConstructor(argsClass.toArray(new Class<?>[0])));
				} catch (Throwable e) {
					error("The '" + className + "' gets constructor error");
				}
			} else {
				error("The '" + className + "' constructor node MUST be more than one argument node!");
			}
		} else {
			try {
				xmlBeanDefinition.setConstructor(clazz.getConstructor(new Class<?>[0]));
			} catch (Throwable e) {
				error("The '" + className + "' gets constructor error");
			}
		}

		// gets all interface name
		String[] names = ReflectUtils.getInterfaceNames(clazz);
		xmlBeanDefinition.setInterfaceNames(names);
		log.debug("class [{}] names size [{}]", className, names.length);

		// gets all properties
		List<Element> properties = dom.elements(ele, PROPERTY_ELEMENT);
		if (properties != null) {
			for (Element property : properties) {
				String name = property.getAttribute(NAME_ATTRIBUTE);
				XmlManagedNode xmlManagedNode = parseXmlManagedNode(property, dom);
				if(xmlManagedNode != null && VerifyUtils.isNotEmpty(name)) {
					xmlBeanDefinition.getProperties().put(name, xmlManagedNode);
				}
			}
		}
		return xmlBeanDefinition;
	}
	
	private XmlManagedNode parseXmlManagedNode(Element property, Dom dom) {
		boolean hasValueAttribute = property.hasAttribute(VALUE_ATTRIBUTE);
		boolean hasRefAttribute = property.hasAttribute(REF_ATTRIBUTE);

		// element types choose one of ref, value, list, etc.
		NodeList nl = property.getChildNodes();
		Element subElement = null;
		for (int i = 0; i < nl.getLength(); ++i) {
			Node node = nl.item(i);
			if (node instanceof Element) {
				if (subElement != null) {
					error("This element must not contain more than one sub-element");
				} else {
					subElement = (Element) node;
				}
			}
		}

		if (hasValueAttribute && hasRefAttribute || 
				((hasValueAttribute || hasRefAttribute) && subElement != null)) {
			error("This element is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element");
		}

		if (hasValueAttribute) {
			// literal value
			String value = property.getAttribute(VALUE_ATTRIBUTE);
			if (!StringUtils.hasText(value)) {
				error("This element contains empty 'value' attribute");
			}
			return new ManagedValue(value);
		} else if (hasRefAttribute) {
			// bean reference
			String ref = property.getAttribute(REF_ATTRIBUTE);
			if (!StringUtils.hasText(ref)) {
				error("This element contains empty 'ref' attribute");
			}
			return new ManagedRef(ref);
		} else if (subElement != null) {
			// sub-elements
			return (XmlManagedNode)XmlNodeStateMachine.stateProcessor(subElement, dom);
		} else {
			error("This element must specify a ref or value");
			return null;
		}
	}
}
